/**
 * <p>Project: </p>
 * <p>File: swap_ext_clock.c</p>
 * <p>Description: </p>
 * <p>Copyright: Copyright (c) 2016</p>
 * @author 王树根
 * @email: wangshugen@ict.ac.cn
 * @date 2016年6月8日
 */

#include <swap_ext_clock.h>
#include <assert.h>
#include <defs.h>
#include <list.h>
#include <memlayout.h>
#include <swap.h>
#include <swapfs.h>
#include <vmm.h>
#include <pmm.h>
#include <stdio.h>

static list_entry_t pra_list_head;

static int _ext_clock_init(void) {
	return 0;
}

static int _ext_clock_init_mm(struct mm_struct *mm) {
	list_init(&pra_list_head);
	mm->sm_priv = &pra_list_head;

	return 0;
}

static int _ext_clock_map_swappable(struct mm_struct *mm, uintptr_t addr,
    struct Page *page, int swap_in) {
	list_entry_t *head = (list_entry_t*) mm->sm_priv;
	list_entry_t *entry = &(page->pra_page_link);

	assert(entry != NULL && head != NULL);

	// 执行按是否被访问、是否被修改进行排序
	list_add_before(head, entry);

	return 0;
}

static int _ext_clock_set_unswappable(struct mm_struct *mm, uintptr_t addr) {
	return 0;
}

static int _ext_clock_swap_out_victim(struct mm_struct *mm,
    struct Page **ptr_page, int in_tick) {
	list_entry_t *head = (list_entry_t*) mm->sm_priv;
	assert(head != NULL);

	if (list_empty(head)) {
		*ptr_page = NULL;
		return 0;
	}

	list_entry_t *le = list_next(head);
	list_del(le);
	*ptr_page = le2page(le, pra_page_link);

	return 0;
}

static int _ext_clock_tick_event(struct mm_struct *mm) {
	// if no more than 1 free page is available, then do swap out action.
	if (pmm_manager->nr_free_pages() > 0) {
		return 0;
	}

	uintptr_t v;
	struct Page *page;
	int ret = _ext_clock_swap_out_victim(mm, &page, 0);
	if (ret != 0) {
		cprintf("swap_out: call swap_out_victim failed\n");
		return ret;
	}

	// assert(!PageReserved(page));

	// cprintf("SWAP: choose victim page 0x%08x\n", page);

	v = page->pra_vaddr;
	pte_t *ptep = get_pte(mm->pgdir, v, 0);
	assert((*ptep & PTE_P) != 0);

	if ((ret = swapfs_write((page->pra_vaddr / PGSIZE + 1) << 8, page)) != 0) {
		cprintf("SWAP: failed to save\n");
		_ext_clock_map_swappable(mm, v, page, 0);
		return ret;
	} else {
		cprintf("swap_out: store page in vaddr 0x%x to disk swap entry %d\n", v,
		    page->pra_vaddr / PGSIZE + 1);
		*ptep = (page->pra_vaddr / PGSIZE + 1) << 8;
		free_page(page);
	}

	tlb_invalidate(mm->pgdir, v);

	return 0;
}

static int _ext_clock_check_swap(void) {
	cprintf("write Virt Page c in ext_clock_check_swap\n");
	*(unsigned char *) 0x3000 = 0x0c;
	assert(pgfault_num == 4);
	cprintf("write Virt Page a in ext_clock_check_swap\n");
	*(unsigned char *) 0x1000 = 0x0a;
	assert(pgfault_num == 4);
	cprintf("write Virt Page d in ext_clock_check_swap\n");
	*(unsigned char *) 0x4000 = 0x0d;
	assert(pgfault_num == 4);
	cprintf("write Virt Page b in ext_clock_check_swap\n");
	*(unsigned char *) 0x2000 = 0x0b;
	assert(pgfault_num == 4);
	cprintf("write Virt Page e in ext_clock_check_swap\n");
	*(unsigned char *) 0x5000 = 0x0e;
	assert(pgfault_num == 5);
	cprintf("write Virt Page b in ext_clock_check_swap\n");
	*(unsigned char *) 0x2000 = 0x0b;
	assert(pgfault_num == 5);
	cprintf("write Virt Page a in ext_clock_check_swap\n");
	*(unsigned char *) 0x1000 = 0x0a;
	assert(pgfault_num == 6);
	cprintf("write Virt Page b in ext_clock_check_swap\n");
	*(unsigned char *) 0x2000 = 0x0b;
	assert(pgfault_num == 7);
	cprintf("write Virt Page c in ext_clock_check_swap\n");
	*(unsigned char *) 0x3000 = 0x0c;
	assert(pgfault_num == 8);
	cprintf("write Virt Page d in ext_clock_check_swap\n");
	*(unsigned char *) 0x4000 = 0x0d;
	assert(pgfault_num == 9);
	cprintf("write Virt Page e in ext_clock_check_swap\n");
	*(unsigned char *) 0x5000 = 0x0e;
	assert(pgfault_num == 10);
	cprintf("write Virt Page a in ext_clock_check_swap\n");
	assert(*(unsigned char * )0x1000 == 0x0a);
	*(unsigned char *) 0x1000 = 0x0a;
	assert(pgfault_num == 11);

	return 0;
}

struct swap_manager swap_manager_ext_clock = {
    .name = "extend clock swap manager",
    .init = &_ext_clock_init,
    .init_mm = &_ext_clock_init_mm,
    .tick_event = &_ext_clock_tick_event,
    .map_swappable = &_ext_clock_map_swappable,
    .set_unswappable = &_ext_clock_set_unswappable,
    .swap_out_victim = &_ext_clock_swap_out_victim,
    .check_swap = &_ext_clock_check_swap,
};
